import {log} from "./SIOSession";

declare global {
    interface Navigator {
        isIosApp: any
    }
}

export class StableWebSocket implements WebSocket {

    private ws: WebSocket
    private isReconnectEnabled = true
    private _generator: () => WebSocket

    private listenerGroups = new Map<string, EventListenerOrEventListenerObject[]>()
    public beforeReconnect: (socket: StableWebSocket) => Promise<void> = () => Promise.resolve();

    public constructor(generator: () => WebSocket);
    public constructor(url: string, protocols?: string | string[]);
    public constructor(generatorOrUrl: any, protocols?: any) {
        if (typeof generatorOrUrl === 'string') {
            this.ws = new WebSocket(generatorOrUrl, protocols);
            this._generator = () => new WebSocket(generatorOrUrl, protocols);
        } else {
            this.ws = generatorOrUrl();
            this._generator = generatorOrUrl;
        }
        this.addEventListener('close', this.reconnectIfNeeds.bind(this));

        if (window.navigator.isIosApp?.() /*|| !!window.navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)*/) {
            this.addEventListener('error', (error) => {
                console.log('connect error', error.type);
                this.reconnectIfNeeds();
            });
            /*document.addEventListener('visibilitychange', (event) => {
                log('visibilitychange isHidden: ', document.hidden, "event: ",document.visibilityState)
                log('connect state', this.ws.readyState)
                log('event', this.ws.readyState)
              //   0 (WebSocket.CONNECTING) 正在链接中
              //   1 (WebSocket.OPEN) 已经链接并且可以通讯
              //   2 (WebSocket.CLOSING) 连接正在关闭
              //   3 (WebSocket.CLOSED) 连接已关闭或者没有链接成功
              //  https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/readyState
              //   if (this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING) {
              //
              //   }
                if (document.visibilityState === 'visible') {
                    log('===进入前台，重新连接')
                    this.reconnectIfNeeds();
                } else {
                    log('===进入后台，断开连接')
                    this.ws.close();
                }
            });*/
        }
    }

    public get CLOSED(): number {
        return WebSocket.CLOSED
    };

    public get CLOSING(): number {
        return WebSocket.CLOSING
    };

    public get CONNECTING(): number {
        return WebSocket.CONNECTING
    };

    public get OPEN(): number {
        return WebSocket.OPEN
    };

    public get binaryType(): BinaryType {
        return this.ws.binaryType
    };

    public set binaryType(val) {
        this.ws.binaryType = val
    };

    public get bufferedAmount(): number {
        return this.ws.bufferedAmount
    };

    public get extensions(): string {
        return this.ws.extensions
    };

    public get onclose(): ((this: WebSocket, ev: CloseEvent) => any) | null {
        return this.ws.onclose
    };

    public set onclose(callback) {
        this.ws.onclose = callback
    };

    public get onerror(): ((this: WebSocket, ev: Event) => any) | null {
        return this.ws.onerror
    };

    public set onerror(callback) {
        this.ws.onerror = callback
    };

    public get onmessage(): ((this: WebSocket, ev: MessageEvent) => any) | null {
        return this.ws.onmessage
    };

    public set onmessage(callback) {
        this.ws.onmessage = callback
    };

    public get onopen(): ((this: WebSocket, ev: Event) => any) | null {
        return this.ws.onopen
    };

    public set onopen(callback) {
        this.ws.onopen = callback
    };

    public get protocol(): string {
        return this.ws.protocol
    };

    public get readyState(): number {
        return this.ws.readyState
    };

    public get url(): string {
        return this.ws.url
    };

    public addEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
    public addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
    public addEventListener(type: any, listener: any, options?: boolean | AddEventListenerOptions): void {
        let listeners = this.listenerGroups.get(type) || [];
        listeners.push(listener);
        this.listenerGroups.set(type, listeners);
        this.ws.addEventListener(type, listener);
    }

    public close(code?: number, reason?: string): void {
        switch (this.ws.readyState) {
            case WebSocket.CONNECTING:
                return console.warn('WebSocket is CONNECTING');
            case WebSocket.OPEN:
                this.isReconnectEnabled = false
                this.ws.close(code, reason)
                break;
            case WebSocket.CLOSING:
                return console.warn('WebSocket is CLOSING');
            case WebSocket.CLOSED:
                return console.warn('WebSocket is CLOSED');
            default:
                break;
        }
    }

    public dispatchEvent(event: Event): boolean {
        return this.ws.dispatchEvent(event);
    }

    public removeEventListener<K extends keyof WebSocketEventMap>(type: K, listener: (this: WebSocket, ev: WebSocketEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
    public removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
    public removeEventListener(type: any, listener: any, options?: boolean | EventListenerOptions): void {
        let listeners = this.listenerGroups.get(type);
        if (!listeners) return;
        let targetIdx = listeners.findIndex((value => value === listener));
        if (targetIdx >= 0) {
            listeners.splice(targetIdx, 1);
        }
        this.listenerGroups.set(type, listeners);
        this.ws.removeEventListener(type, listener);
    }

    public send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {
        switch (this.ws.readyState) {
            case WebSocket.CONNECTING:
                return console.warn('WebSocket is CONNECTING');
            case WebSocket.OPEN:
                this.ws.send(data);
                break;
            case WebSocket.CLOSING:
                return console.warn('WebSocket is CLOSING');
            case WebSocket.CLOSED:
                return console.warn('WebSocket is CLOSED');
            default:
                break;
        }
    }

    private reconnectIfNeeds() {
        if (this.isReconnectEnabled) {
            this.beforeReconnect(this).then(this.doReconnect.bind(this));
        }
        // switch (event.code) {
        //     case 1001: //CLOSE_GOING_AWAY
        //     case 1011: //Internal Error
        //     case 1012: //Service Restart
        //     case 1013: //Try Again Later
        //         this.doReconnect();
        //         break;
        //     default: //不必重连
        //         break;
        // }
    }

    private doReconnect() {
        log('尝试重连...');
        let oldWS = this.ws;
        this.ws = this._generator();
        this.ws.onopen = oldWS.onopen;
        this.ws.onmessage = oldWS.onmessage;
        this.ws.onclose = oldWS.onclose;
        this.ws.onerror = oldWS.onerror;
        this.listenerGroups.forEach((listeners, type) =>
            listeners.forEach(listener =>
                this.ws.addEventListener(type, listener
                )
            )
        )
    }
}
